//
//  PlayerViewController.swift
//  SwiftCode
//
//  Created by 于虎拴 on 2023/2/21.
//

import Foundation
import UIKit
import AVFoundation

class PlayerViewController: UIViewController {
  
  // MARK: Properties
  let player = AVPlayer()
  
  /// 时间观察器，从 player 的 addPeriodicTimeObserverForInterval(_:queue:usingBlock:) 方法获得的令牌。
  private var timeObserverToken: Any?
  
  /// 控制 — 暂停/播放 AVPlayer.timeControlStatus
  private var playerTimeControlStatusObserver: NSKeyValueObservation?
  
  /// 快进 — 支持大于 1.0 的速率值 AVPlayer.currentItem?.canPlayFastForward
  private var playerItemFastForwardObserver: NSKeyValueObservation?
  
  /// 倒带 — 支持 -1.0 的速率值，AVPlayer.currentItem?.canPlayReverse
  private var playerItemReverseObserver: NSKeyValueObservation?
  
  /// 倒带 — 支持从 0.0 到 -1.0 的速率值。AVPlayer.currentItem?.canPlaySlowReverse
  private var playerItemSlowReverseObserver: NSKeyValueObservation?
  
  /// 倒带 — 支持小于 -1.0 的速率值，AVPlayer.currentItem?.canPlayFastReverse
  private var playerItemFastReverseObserver: NSKeyValueObservation?
  
  /**
   AVPlayer.currentItem?.status 属性指示播放器项目的播放准备情况。
   
   将播放器项目与播放器相关联会立即开始将项目的媒体排队并准备播放，但您必须等到其状态更改为 .readyToPlay 才能使用。
   */
  private var playerItemStatusObserver: NSKeyValueObservation?
  
  private let url: URL
  
  public init(_ url: URL) {
    self.url = url
    super.init(nibName: nil, bundle: nil)
    modalPresentationStyle = .currentContext
  }
  
  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  override var preferredStatusBarStyle: UIStatusBarStyle {
    return .lightContent
  }
  
  // MARK: View Controller
  override func viewDidLoad() {
    super.viewDidLoad()
    
    initView()
    initData()
  }
  
  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    if navigationController?.isNavigationBarHidden == false {
      dismissButton.setImage(.full_screen, for: .normal)
    }
  }
  
  override func viewDidDisappear(_ animated: Bool) {
    player.pause()
    
    if let timeObserverToken = timeObserverToken {
      player.removeTimeObserver(timeObserverToken)
      self.timeObserverToken = nil
    }
  }
  
  private func initView() {
    view.backgroundColor = .black
    [playerView, controlView].forEach { view.addSubview($0) }
    playerView.snp.makeConstraints { $0.edges.equalToSuperview() }
    controlView.snp.makeConstraints { $0.edges.equalToSuperview() }
    
    // controlView
    [dismissButton, playPauseButton].forEach { controlView.addSubview($0) }
    dismissButton.snp.makeConstraints { make in
      make.top.leading.equalTo(view.safeAreaLayoutGuide).offset(16)
    }
    playPauseButton.snp.makeConstraints { make in
      make.center.equalToSuperview()
    }
  }
  
  private func initData() {
    // Create an asset instance to represent the media file.
    let asset = AVAsset(url: url)
    
    loadPropertyValues(forAsset: asset)
  }
  
  /// 在尝试播放之前加载指定keys（属性名称）的值。
  /// Asset 初始化不保证所有属性的可用。 使用“AVAsynchronousKeyValueLoading”协议通过完成处理程序获得答案，而不是在计算值时阻塞当前线程。
  func loadPropertyValues(forAsset asset: AVAsset) {
    /// 在播放开始之前加载并测试以下资源keys。
    let assetKeysRequiredToPlay = [
      "playable", // 如果“playable”属性值等于“true”，您可以使用资源初始化播放器项目的实例。
      "hasProtectedContent" // 如果“hasProtectedContent”属性值等于“true”，则资源包含受保护的内容并且无法播放。
    ]
    // 使用 `AVAsset` 存在在系统填充属性时阻塞当前线程（主 UI 线程）的风险。 推迟此类工作，直到系统加载您需要的属性。
    asset.loadValuesAsynchronously(forKeys: assetKeysRequiredToPlay) {
      // 资源在任意队列上调用其完成处理程序。 将处理程序分派到主队列以避免多个线程同时使用内部状态。
      DispatchQueue.main.async {
        // 确认可以播放
        if self.validateValues(forKeys: assetKeysRequiredToPlay, forAsset: asset) {
          // 设置播放器观察者
          self.setupPlayerObservers()
          // 设置播放器的视觉输出。使用 `AVPlayerLayer` 对象来管理。
          self.playerView.player = self.player
          // 仅给定 AVAsset，AVPlayer 无法执行播放。 AVAsset 仅对媒体的静态方面进行建模，例如它的持续时间。
          // 要播放资源，需要创建 AVPlayerItem 的实例并使其成为播放器的当前项目。该对象对 AVPlayer 实例播放的资源的时间和呈现状态进行建模。
          self.player.replaceCurrentItem(with: AVPlayerItem(asset: asset))
        }
      }
    }
  }
  
  /**
   确认资源所有keys已加载并验证它们的值。
   */
  func validateValues(forKeys keys: [String], forAsset asset: AVAsset) -> Bool {
    for key in keys {
      var error: NSError?
      if asset.statusOfValue(forKey: key, error: &error) == .failed {
        let stringFormat = NSLocalizedString("The media failed to load the key \"%@\"",
                                             comment: "You can't use this AVAsset because one of it's keys failed to load.")
        let message = String.localizedStringWithFormat(stringFormat, key)
        handleErrorWithMessage(message, error: error)
        
        return false
      }
    }
    if !asset.isPlayable || asset.hasProtectedContent {
      let message = NSLocalizedString("The media isn't playable or it contains protected content.",
                                      comment: "You can't use this AVAsset because it isn't playable or it contains protected content.")
      handleErrorWithMessage(message)
      return false
    }
    return true
  }
  
  // MARK: - Key-Value Observing
  /**
   在应用程序所需的各种播放器和播放器项目属性上设置一些键值观察器，例如快进、倒退播放的能力等。
   
   观察者根据观察到的属性值调整样本用户界面元素的状态。
   */
  func setupPlayerObservers() {
    // 创建一个观察者来切换播放/暂停按钮控件图标，以反映播放器的“timeControStatus”属性的播放状态。
    playerTimeControlStatusObserver = player.observe(\AVPlayer.timeControlStatus,
                                                      options: [.initial, .new]) { [weak self] _, _ in
      guard let self else { return }
      // 防止非主线程
      DispatchQueue.main.async {
        self.setPlayPauseButtonImage()
      }
    }
    // 创建一个定期观察者以在播放期间更新电影播放器时间滑块。
    let interval = CMTime(value: 1, timescale: 2)
    timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval,
                                                       queue: .main) { [unowned self] time in
      let timeElapsed = Float(time.seconds)
      self.timeSlider.value = timeElapsed
      self.startTimeLabel.text = self.createTimeString(time: timeElapsed)
    }
    
    // 在播放器的“canPlayFastForward”属性上创建一个观察器，以设置快进按钮启用状态。
    playerItemFastForwardObserver = player.observe(\AVPlayer.currentItem?.canPlayFastForward,
                                                    options: [.new, .initial]) { [unowned self] player, change in
      DispatchQueue.main.async {
        self.fastForwardButton.isEnabled = player.currentItem?.canPlayFastForward ?? false
      }
    }
    playerItemReverseObserver = player.observe(\AVPlayer.currentItem?.canPlayReverse,
                                                options: [.new, .initial]) { [unowned self] player, _ in
      DispatchQueue.main.async {
        self.rewindButton.isEnabled = player.currentItem?.canPlayReverse ?? false
      }
    }
    playerItemFastReverseObserver = player.observe(\AVPlayer.currentItem?.canPlayFastReverse,
                                                    options: [.new, .initial]) { [unowned self] player, _ in
      DispatchQueue.main.async {
        self.rewindButton.isEnabled = player.currentItem?.canPlayFastReverse ?? false
      }
    }
    // 等到其状态更改为 .readyToPlay 才能使用。
    playerItemStatusObserver = player.observe(\AVPlayer.currentItem?.status,
                                               options: [.new, .initial]) { [unowned self] _, _ in
      DispatchQueue.main.async {
        self.updateUIforPlayerItemStatus()
      }
    }
  }
  
  // MARK: - Actions of Button
  @objc func dismiss(_ sender: UIButton) {
    if navigationController?.isNavigationBarHidden == false {
      navigationController?.setNavigationBarHidden(true, animated: true)
      dismissButton.setImage(.close, for: .normal)
      return
    }
    if let navigationController {
      navigationController.popViewController(animated: true)
      return
    }
    dismiss(animated: true)
  }
  
  @objc private func togglePlay(_ sender: UIButton) {
    switch player.timeControlStatus {
    case .playing:
      player.pause()
    case .paused:
      player.play()
    default:
      player.pause()
    }
  }
  
  @objc private func toggleHidden(_ recognizer: UIGestureRecognizer) {
    Timber.d("toggleHidden")
    controlView.isHidden.toggle()
    delayHidden()
  }
  
  func delayHidden() {
    if let hiddenControlItem { hiddenControlItem.cancel() }
    if !controlView.isHidden {
      let workItem = DispatchWorkItem { [weak self] in
        self?.controlView.isHidden = true
      }
      DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3), execute: workItem)
      self.hiddenControlItem = workItem
    }
  }
  
  private var hiddenControlItem: DispatchWorkItem?
  
  // MARK: - Error Handling
  func handleErrorWithMessage(_ message: String, error: Error? = nil) {
    if let err = error {
      print("Error occurred with message: \(message), error: \(err).")
    }
    let alertTitle = NSLocalizedString("Error", comment: "Alert title for errors")
    
    let alert = UIAlertController(title: alertTitle, message: message,
                                  preferredStyle: UIAlertController.Style.alert)
    let alertActionTitle = NSLocalizedString("OK", comment: "OK on error alert")
    let alertAction = UIAlertAction(title: alertActionTitle, style: .default, handler: nil)
    alert.addAction(alertAction)
    present(alert, animated: true, completion: nil)
  }
  
  // MARK: - Utilities
  func createTimeString(time: Float) -> String {
    let components = NSDateComponents()
    components.second = Int(max(0.0, time))
    return timeRemainingFormatter.string(from: components as DateComponents)!
  }
  
  /// 调整播放/暂停按钮图像以反映当前播放状态。
  func setPlayPauseButtonImage() {
    var buttonImage: UIImage?
    switch player.timeControlStatus {
    case .playing:
      buttonImage = .pause_fill
    case .paused, .waitingToPlayAtSpecifiedRate:
      buttonImage = .play_fill
    @unknown default:
      assertionFailure("timeControlStatus unknown")
    }
    guard let buttonImage else { return }
    playPauseButton.setImage(buttonImage, for: .normal)
    delayHidden()
  }
  
  func updateUIforPlayerItemStatus() {
    guard let currentItem = player.currentItem else { return }
    
    switch currentItem.status {
    case .failed:
      // 显示error
      playPauseButton.isEnabled = false
      timeSlider.isEnabled = false
      startTimeLabel.isEnabled = false
      durationLabel.isEnabled = false
      handleErrorWithMessage(currentItem.error?.localizedDescription ?? "", error: currentItem.error)
      
    case .readyToPlay:
      // 启用播放/暂停按钮
      playPauseButton.isEnabled = true
      // 更新播放器持续时间的时间滑块控件、开始时间和持续时间标签。
      
      let newDurationSeconds = Float(currentItem.duration.seconds)
      let currentTime = Float(CMTimeGetSeconds(player.currentTime()))
      
      timeSlider.maximumValue = newDurationSeconds
      timeSlider.value = currentTime
      timeSlider.isEnabled = true
      startTimeLabel.isEnabled = true
      startTimeLabel.text = createTimeString(time: currentTime)
      durationLabel.isEnabled = true
      durationLabel.text = createTimeString(time: newDurationSeconds)
      
    default:
      playPauseButton.isEnabled = false
      timeSlider.isEnabled = false
      startTimeLabel.isEnabled = false
      durationLabel.isEnabled = false
    }
  }
  
  /// 一种格式化程序，它为应用程序 UI 中显示的开始时间和持续时间媒体时间值提供用户可读的字符串。
  let timeRemainingFormatter: DateComponentsFormatter = {
    let formatter = DateComponentsFormatter()
    formatter.zeroFormattingBehavior = .pad
    formatter.allowedUnits = [.minute, .second]
    return formatter
  }()
  
  // MARK: - UI properties
  private lazy var dismissButton: UIButton = {
    let button = UIButton()
    button.setImage(.close, for: .normal)
    button.addTarget(self, action: #selector(dismiss(_:)), for: .touchUpInside)
    return button
  }()
  
  private lazy var playerView: PlayerView = {
    let view = PlayerView()
    view.isUserInteractionEnabled = true
    view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(toggleHidden(_:))))
    return view
  }()
  
  private lazy var controlView: UIView = {
    let view = UIView()
    view.isHidden = true
    view.isUserInteractionEnabled = true
    view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(toggleHidden(_:))))
    return view
  }()
  
  private lazy var timeSlider: UISlider = {
    return UISlider()
  }()
  
  private lazy var startTimeLabel: UILabel = {
    return UILabel()
  }()
  
  private lazy var durationLabel: UILabel = {
    return UILabel()
  }()
  
  private lazy var rewindButton: UIButton = {
    return UIButton()
  }()
  
  private lazy var playPauseButton: UIButton = {
    let button = UIButton()
    button.addTarget(self, action: #selector(togglePlay(_:)), for: .touchUpInside)
    return button
  }()
  
  private lazy var fastForwardButton: UIButton = {
    return UIButton()
  }()
}

fileprivate extension UIImage {
  static let close = UIImage(systemName: "xmark")?
    .withTintColor(UIColor.white, renderingMode: .alwaysOriginal)
    .withConfiguration(UIImage.SymbolConfiguration(pointSize: 20))
  
  static let full_screen = UIImage(systemName: "arrow.up.left.and.arrow.down.right")?
    .withTintColor(UIColor.white, renderingMode: .alwaysOriginal)
    .withConfiguration(UIImage.SymbolConfiguration(pointSize: 20))
  
  static let play_fill = UIImage(systemName: "play.fill")?
    .withTintColor(UIColor.white, renderingMode: .alwaysOriginal)
    .withConfiguration(UIImage.SymbolConfiguration(pointSize: 48))
  
  static let pause_fill = UIImage(systemName: "pause.fill")?
    .withTintColor(UIColor.white, renderingMode: .alwaysOriginal)
    .withConfiguration(UIImage.SymbolConfiguration(pointSize: 48))
}
